#include <jni.h>
#include <string>
#include <sys/system_properties.h>
#include <exception>
#include "aes.h"

#define LOG_TAG "lyn"
#undef  LOGD
#define LOGD(...)    __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#undef  LOGI
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG ,__VA_ARGS__)
#undef  LOGW
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG ,__VA_ARGS__)
#undef  LOGE
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG ,__VA_ARGS__)
#undef  LOGF
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG ,__VA_ARGS__)

static jbyte *secret = (jbyte *) "gsdahgsghsakghsg";

static bool isAuth = false;

jstring createUUID(JNIEnv *env) {
    jclass uuid_jclass = env->FindClass("java/util/UUID");
    jmethodID randomUUID_jmethodID = env->GetStaticMethodID(uuid_jclass, "randomUUID",
                                                            "()Ljava/util/UUID;");
    jobject uuid_jobject = env->CallStaticObjectMethod(uuid_jclass, randomUUID_jmethodID);

    jmethodID toString_jmethodID = env->GetMethodID(uuid_jclass, "toString",
                                                    "()Ljava/lang/String;");

    auto toString_jstring = static_cast<jstring>(env->CallObjectMethod(uuid_jobject,
                                                                       toString_jmethodID));
    jclass string_jclass = env->FindClass("java/lang/String");
    jmethodID replace_jmethodID = env->GetMethodID(string_jclass, "replace",
                                                   "(CC)Ljava/lang/String;");

    auto result = static_cast<jstring>(env->CallObjectMethod(toString_jstring, replace_jmethodID,
                                                             '-', 'a'));

    char result_char[PROP_VALUE_MAX];

    const char *result_c = env->GetStringUTFChars(result, nullptr);

    stpcpy(result_char, result_c);
    return env->NewStringUTF(result_char);
}


jstring encryptionString(JNIEnv *env, jclass thiz, jstring clear_text) {
    if (!isAuth) {
        return nullptr;
    }
    jclass cipherClass = env->FindClass("javax/crypto/Cipher");
    jmethodID cipher_getInstance_methodId =
            env->GetStaticMethodID(cipherClass,
                                   "getInstance",
                                   "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
    jobject cipher = env->CallStaticObjectMethod(cipherClass,
                                                 cipher_getInstance_methodId,
                                                 env->NewStringUTF("AES/ECB/PKCS5Padding"));
    jclass secretKeySpecClass = env->FindClass("javax/crypto/spec/SecretKeySpec");
    jmethodID secretKeySpec_init_methodId =
            env->GetMethodID(secretKeySpecClass,
                             "<init>",
                             "([BLjava/lang/String;)V");
    jbyteArray keyBytes = env->NewByteArray(16);
    env->SetByteArrayRegion(keyBytes,
                            0,
                            16,
                            secret);
    jobject secretKeySpec = env->NewObject(secretKeySpecClass,
                                           secretKeySpec_init_methodId,
                                           keyBytes, env->NewStringUTF("AES"));

    jmethodID cipher_init_methodId = env->GetMethodID(cipherClass, "init",
                                                      "(ILjava/security/Key;)V");
    env->CallVoidMethod(cipher, cipher_init_methodId, 1, secretKeySpec);

    jclass stringClass = env->FindClass("java/lang/String");
    jmethodID getBytes_methodId = env->GetMethodID(stringClass,
                                                   "getBytes",
                                                   "()[B");
    auto inputBytes = (jbyteArray) env->CallObjectMethod(clear_text, getBytes_methodId);

    jmethodID doFinal_methodId = env->GetMethodID(cipherClass, "doFinal", "([B)[B");
    auto encryptedBytes = (jbyteArray) env->CallObjectMethod(cipher,
                                                             doFinal_methodId,
                                                             inputBytes);

    jclass base64Class = env->FindClass("android/util/Base64");
    jmethodID encodeToStringMethodId = env->GetStaticMethodID(base64Class,
                                                              "encodeToString",
                                                              "([BI)Ljava/lang/String;");
    auto str = (jstring) env->
            CallStaticObjectMethod(base64Class,
                                   encodeToStringMethodId, encryptedBytes, 0);
    jmethodID trimMethodId = env->GetMethodID(stringClass, "trim", "()Ljava/lang/String;");
    return (jstring) env->CallObjectMethod(str, trimMethodId);
}


jstring decryptionAuth(JNIEnv *env, jclass thiz, jstring ciphertext) {
    jclass cipherClass = env->FindClass("javax/crypto/Cipher");
    jmethodID cipher_getInstance_methodId =
            env->GetStaticMethodID(cipherClass,
                                   "getInstance",
                                   "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
    jobject cipher = env->CallStaticObjectMethod(cipherClass,
                                                 cipher_getInstance_methodId,
                                                 env->NewStringUTF("AES/ECB/PKCS5Padding"));

    jclass secretKeySpecClass = env->FindClass("javax/crypto/spec/SecretKeySpec");
    jmethodID secretKeySpec_init_methodId =
            env->GetMethodID(secretKeySpecClass,
                             "<init>",
                             "([BLjava/lang/String;)V");
    jbyteArray keyBytes = env->NewByteArray(16);
    env->SetByteArrayRegion(keyBytes,
                            0,
                            16,
                            secret);
    jobject secretKeySpec = env->NewObject(secretKeySpecClass,
                                           secretKeySpec_init_methodId,
                                           keyBytes, env->NewStringUTF("AES"));

    jmethodID cipher_init_methodId = env->GetMethodID(cipherClass, "init",
                                                      "(ILjava/security/Key;)V");
    env->CallVoidMethod(cipher, cipher_init_methodId, 2, secretKeySpec);

    jclass stringClass = env->FindClass("java/lang/String");

    jmethodID trimMethodId = env->GetMethodID(stringClass, "trim", "()Ljava/lang/String;");
    auto str = (jstring) env->CallObjectMethod(ciphertext, trimMethodId);

    jclass base64Class = env->FindClass("android/util/Base64");
    jmethodID encodeMethodId = env->GetStaticMethodID(base64Class,
                                                      "decode",
                                                      "(Ljava/lang/String;I)[B");
    auto base64 = (jbyteArray) env->
            CallStaticObjectMethod(base64Class,
                                   encodeMethodId, str, 0);

    jmethodID doFinal_methodId = env->GetMethodID(cipherClass, "doFinal", "([B)[B");
    auto byteArray = (jbyteArray) env->CallObjectMethod(cipher, doFinal_methodId, base64);
    jmethodID stringConstructor = env->GetMethodID(stringClass, "<init>", "([B)V");
    auto result = (jstring) env->NewObject(stringClass, stringConstructor, byteArray);
    return result;
}

jstring decryptionString(JNIEnv *env, jclass thiz, jstring ciphertext) {
    if (!isAuth) {
        return nullptr;
    }
    jclass cipherClass = env->FindClass("javax/crypto/Cipher");
    jmethodID cipher_getInstance_methodId =
            env->GetStaticMethodID(cipherClass,
                                   "getInstance",
                                   "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
    jobject cipher = env->CallStaticObjectMethod(cipherClass,
                                                 cipher_getInstance_methodId,
                                                 env->NewStringUTF("AES/ECB/PKCS5Padding"));

    jclass secretKeySpecClass = env->FindClass("javax/crypto/spec/SecretKeySpec");
    jmethodID secretKeySpec_init_methodId =
            env->GetMethodID(secretKeySpecClass,
                             "<init>",
                             "([BLjava/lang/String;)V");
    jbyteArray keyBytes = env->NewByteArray(16);
    env->SetByteArrayRegion(keyBytes,
                            0,
                            16,
                            secret);
    jobject secretKeySpec = env->NewObject(secretKeySpecClass,
                                           secretKeySpec_init_methodId,
                                           keyBytes, env->NewStringUTF("AES"));

    jmethodID cipher_init_methodId = env->GetMethodID(cipherClass, "init",
                                                      "(ILjava/security/Key;)V");
    env->CallVoidMethod(cipher, cipher_init_methodId, 2, secretKeySpec);

    jclass stringClass = env->FindClass("java/lang/String");

    jmethodID trimMethodId = env->GetMethodID(stringClass, "trim", "()Ljava/lang/String;");
    auto str = (jstring) env->CallObjectMethod(ciphertext, trimMethodId);

    jclass base64Class = env->FindClass("android/util/Base64");
    jmethodID encodeMethodId = env->GetStaticMethodID(base64Class,
                                                      "decode",
                                                      "(Ljava/lang/String;I)[B");
    auto base64 = (jbyteArray) env->
            CallStaticObjectMethod(base64Class,
                                   encodeMethodId, str, 0);

    jmethodID doFinal_methodId = env->GetMethodID(cipherClass, "doFinal", "([B)[B");
    auto byteArray = (jbyteArray) env->CallObjectMethod(cipher, doFinal_methodId, base64);
    jmethodID stringConstructor = env->GetMethodID(stringClass, "<init>", "([B)V");
    auto result = (jstring) env->NewObject(stringClass, stringConstructor, byteArray);
    return result;
}

jbyteArray encryptionByteArray(JNIEnv *env, jclass clazz, jbyteArray clear_byte_array) {
    if (!isAuth) {
        return nullptr;
    }
    jclass cipherClass = env->FindClass("javax/crypto/Cipher");
    jmethodID cipher_getInstance_methodId =
            env->GetStaticMethodID(cipherClass,
                                   "getInstance",
                                   "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
    jobject cipher = env->CallStaticObjectMethod(cipherClass,
                                                 cipher_getInstance_methodId,
                                                 env->NewStringUTF("AES/ECB/PKCS5Padding"));
    jclass secretKeySpecClass = env->FindClass("javax/crypto/spec/SecretKeySpec");
    jmethodID secretKeySpec_init_methodId =
            env->GetMethodID(secretKeySpecClass,
                             "<init>",
                             "([BLjava/lang/String;)V");
    jbyteArray keyBytes = env->NewByteArray(16);
    env->SetByteArrayRegion(keyBytes,
                            0,
                            16,
                            secret);
    jobject secretKeySpec = env->NewObject(secretKeySpecClass,
                                           secretKeySpec_init_methodId,
                                           keyBytes, env->NewStringUTF("AES"));

    jmethodID cipher_init_methodId = env->GetMethodID(cipherClass, "init",
                                                      "(ILjava/security/Key;)V");
    env->CallVoidMethod(cipher, cipher_init_methodId, 1, secretKeySpec);


    jmethodID doFinal_methodId = env->GetMethodID(cipherClass, "doFinal", "([B)[B");
    auto encryptedBytes = (jbyteArray) env->CallObjectMethod(cipher,
                                                             doFinal_methodId,
                                                             clear_byte_array);
    return encryptedBytes;
}

jbyteArray decryptionByteArray(JNIEnv *env, jclass clazz, jbyteArray clear_byte_array) {
    if (!isAuth) {
        return nullptr;
    }
    jclass cipherClass = env->FindClass("javax/crypto/Cipher");
    jmethodID cipher_getInstance_methodId =
            env->GetStaticMethodID(cipherClass,
                                   "getInstance",
                                   "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
    jobject cipher = env->CallStaticObjectMethod(cipherClass,
                                                 cipher_getInstance_methodId,
                                                 env->NewStringUTF("AES/ECB/PKCS5Padding"));

    jclass secretKeySpecClass = env->FindClass("javax/crypto/spec/SecretKeySpec");
    jmethodID secretKeySpec_init_methodId =
            env->GetMethodID(secretKeySpecClass,
                             "<init>",
                             "([BLjava/lang/String;)V");
    jbyteArray keyBytes = env->NewByteArray(16);
    env->SetByteArrayRegion(keyBytes,
                            0,
                            16,
                            secret);
    jobject secretKeySpec = env->NewObject(secretKeySpecClass,
                                           secretKeySpec_init_methodId,
                                           keyBytes, env->NewStringUTF("AES"));

    jmethodID cipher_init_methodId = env->GetMethodID(cipherClass, "init",
                                                      "(ILjava/security/Key;)V");
    env->CallVoidMethod(cipher, cipher_init_methodId, 2, secretKeySpec);

    jmethodID doFinal_methodId = env->GetMethodID(cipherClass, "doFinal", "([B)[B");
    auto byteArray = (jbyteArray) env->CallObjectMethod(cipher, doFinal_methodId, clear_byte_array);
    return byteArray;
}


jstring getSn(JNIEnv *env, jclass thiz) {
    char value[PROP_VALUE_MAX];
    __system_property_get("ro.serialno",value);
    return env->NewStringUTF(value);
}

jboolean init(JNIEnv *env, jclass thiz, jstring auth) {
    try {
        jstring sn = getSn(env, thiz);
        jstring decryption_sn = decryptionAuth(env, thiz, auth);
        const char *sn_ = env->GetStringUTFChars(sn, nullptr);
        const char *decryption_sn_ = env->GetStringUTFChars(decryption_sn, nullptr);
        int result = strcmp(sn_, decryption_sn_);
        if (result == 0) {
            isAuth = true;
            return true;
        }
    } catch (std::exception e) {
    }
    return false;
}


static JNINativeMethod nativeMethod[] = {
        {"getSn", "()Ljava/lang/String;", (void *) getSn},
        {"init", "(Ljava/lang/String;)Z", (void *) init},
        {"encryptionString",    "(Ljava/lang/String;)Ljava/lang/String;",         (void *) encryptionString},
        {"decryptionString",    "(Ljava/lang/String;)Ljava/lang/String;",         (void *) decryptionString},
        {"encryptionByteArray", "([B)[B",                                         (void *) encryptionByteArray},
        {"decryptionByteArray", "([B)[B",                                         (void *) decryptionByteArray}
};

//注册函数
static void registerNativeMethod(JNIEnv *env) {
    jclass class_text = env->FindClass("com/lyn/aes/Aes");
    for (int i = 0; i < 6; ++i) {
        env->RegisterNatives(class_text, nativeMethod,
                             sizeof(nativeMethod) / sizeof(nativeMethod[i]));
    }
}


int JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = nullptr;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_1) == JNI_OK) {
        registerNativeMethod(env);
    }
    return JNI_VERSION_1_6;

}


